// Package admin
// @Link  https://github.com/bufanyun/hotgo
// @Copyright  Copyright (c) 2023 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License  https://github.com/bufanyun/hotgo/blob/master/LICENSE
package admin

import (
	"context"
	"fmt"
	"github.com/go-pay/gopay/wechat/v3"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/util/gconv"
	"hotgo/internal/consts"
	"hotgo/internal/dao"
	"hotgo/internal/library/contexts"
	"hotgo/internal/library/hgorm"
	"hotgo/internal/library/location"
	"hotgo/internal/library/payment"
	"hotgo/internal/model"
	"hotgo/internal/model/entity"
	"hotgo/internal/model/input/adminin"
	"hotgo/internal/model/input/payin"
	"hotgo/internal/model/input/sysin"
	"hotgo/internal/service"
	"hotgo/utility/joinpay"
	"time"
)

type sAdminCash struct{}

func NewAdminCash() *sAdminCash {
	return &sAdminCash{}
}

func init() {
	service.RegisterAdminCash(NewAdminCash())
}

// View 获取指定提现信息
func (s *sAdminCash) View(ctx context.Context, in *adminin.CashViewInp) (res *adminin.CashViewModel, err error) {
	// 这里做了强制限制非超管不允许访问，如果你想通过菜单权限控制，请注释掉以下验证
	if !service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx)) {
		err = gerror.New("没有访问权限")
		return
	}

	if err = dao.AdminCash.Ctx(ctx).Where("id", in.Id).Scan(&res); err != nil {
		err = gerror.Wrap(err, consts.ErrorORM)
		return
	}

	if res == nil {
		err = gerror.New("提现信息获取失败")
		return
	}

	var mem *entity.AdminMember
	err = dao.AdminMember.Ctx(ctx).Where("id", res.MemberId).Scan(&mem)
	if err != nil {
		err = gerror.Wrap(err, consts.ErrorORM)
		return
	}

	if !mem.Cash.IsNil() {
		if err = mem.Cash.Scan(&res.MemberCash); err != nil {
			return
		}
	}
	return
}

// List 获取列表
func (s *sAdminCash) List(ctx context.Context, in *adminin.CashListInp) (list []*adminin.CashListModel, totalCount int, err error) {
	var (
		mod = dao.AdminCash.Ctx(ctx)
		//opMemberId = contexts.GetUserId(ctx)
		//isSuper    = service.AdminMember().VerifySuperId(ctx, opMemberId)
	)

	mod = mod.FieldsPrefix(dao.AdminCash.Table(), adminin.CashListModel{})
	mod = mod.Fields(hgorm.JoinFields(ctx, adminin.CashListModel{}, &dao.AdminMember, "adminMember"))
	mod = mod.Fields(hgorm.JoinFields(ctx, adminin.CashListModel{}, &dao.Store, "store"))
	mod = mod.Fields()

	// 关联表字段
	mod = mod.LeftJoinOnFields(dao.AdminMember.Table(), dao.AdminCash.Columns().MemberId, "=", dao.AdminMember.Columns().Id)
	mod = mod.LeftJoinOnFields(dao.Store.Table(), dao.AdminCash.Columns().StoreId, "=", dao.Store.Columns().Id)

	if in.MemberId > 0 {
		mod = mod.Where("member_id", in.MemberId)
	}

	if in.MemberName != "" {
		mod = mod.WhereOrPrefixLike(dao.AdminMember.Table(), dao.AdminMember.Columns().Nickname, "%"+in.MemberName+"%")
		mod = mod.WhereOrPrefixLike(dao.AdminMember.Table(), dao.AdminMember.Columns().RealName, "%"+in.MemberName+"%")
	}

	if len(in.CreatedAt) == 2 {
		mod = mod.WhereBetween("created_at", gtime.New(in.CreatedAt[0]), gtime.New(in.CreatedAt[1]))
	}

	if in.StoreName != "" {
		mod = mod.WhereLike(dao.Store.Table()+".name", "%"+in.StoreName+"%")
	}

	// 请求方式
	if in.Status > 0 {
		mod = mod.Where("status", in.Status)
	}

	//if !isSuper {
	//	mod = mod.Where("member_id", opMemberId)
	//}

	if err = mod.Page(in.Page, in.PerPage).OrderDesc(dao.AdminCash.Table()+"."+dao.AdminCash.Columns().Id).ScanAndCount(&list, &totalCount, false); err != nil {
		err = gerror.Wrap(err, consts.ErrorORM)
		return
	}
	return
}

// Apply 申请提现
func (s *sAdminCash) Apply(ctx context.Context, in *adminin.CashApplyInp) (err error) {
	var (
		config *model.CashConfig
		member *entity.AdminMember
	)

	if in.Money <= 0 {
		err = gerror.New("请输入提现金额")
		return
	}

	if in.Money != float64(int(in.Money)) {
		err = gerror.New("提现金额必须是正整数")
		return
	}

	count, err := dao.AdminCash.Ctx(ctx).
		Where("member_id", in.MemberId).
		Where("status", consts.StatusEnabled).
		Count()
	if err != nil {
		return
	}

	if count > 0 {
		err = gerror.New("存在正在处理中的提现，请耐心等待处理后再试！")
		return
	}

	if err = dao.AdminMember.Ctx(ctx).Where("id", in.MemberId).Scan(&member); err != nil {
		err = gerror.Newf("获取用户信息失败:%+v", err.Error())
		return
	}

	if member == nil {
		err = gerror.Newf("获取用户信息失败")
		return
	}

	if member.Balance < in.Money {
		err = gerror.Newf("余额不足")
		return
	}

	// 提现信息
	var cash adminin.MemberCash
	if member.Cash.IsNil() {
		err = gerror.Newf("请先设置提现账户！")
		return
	}

	if err = gconv.Scan(member.Cash, &cash); err != nil {
		return
	}

	if cash.Name == "" || cash.Account == "" || cash.PayeeCode == "" {
		err = gerror.New("请设置完整的提现账户信息！")
		return
	}

	conf, err := service.SysConfig().GetConfigByGroup(ctx, &sysin.GetConfigInp{Group: "cash"})
	if err != nil {
		return
	}

	if err = gconv.Struct(conf.List, &config); err != nil {
		return err
	}

	if !config.Switch {
		err = gerror.New("提现通道正在升级维护中，请稍后再试！")
		return
	}

	if in.Money < config.MinMoney {
		err = gerror.Newf("单次提现金额不能低于 %v 元", config.MinMoney)
		return
	}

	fee := in.Money * config.MinFeeRatio
	if fee < config.MinFee {
		fee = config.MinFee
	}

	lastMoney := in.Money - fee
	if lastMoney <= 1 {
		err = gerror.Newf("提现金额过少，请增加提现金额！")
		return
	}

	err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
		// 添加提现记录
		lastInsertId, err := dao.AdminCash.Ctx(ctx).Data(g.Map{
			"member_id":  in.MemberId,
			"money":      in.Money,
			"fee":        fee,
			"last_money": lastMoney,
			"created_at": gtime.Now(),
			"status":     consts.CashStatusWait,
			"msg":        "",
			"ip":         location.GetClientIp(ghttp.RequestFromCtx(ctx)),
		}).OmitEmptyData().InsertAndGetId()
		if err != nil {
			return
		}

		// 更新余额
		_, err = service.AdminCreditsLog().SaveBalance(ctx, &adminin.CreditsLogSaveBalanceInp{
			MemberId:    in.MemberId,
			AppId:       contexts.GetModule(ctx),
			AddonsName:  contexts.GetAddonName(ctx),
			CreditGroup: consts.CreditGroupApplyCash,
			Num:         -in.Money,
			MapId:       lastInsertId,
			Remark:      "后台申请提现",
		})

		return
	})

	if err != nil {
		err = gerror.Newf("申请提现失败, %+v", err)
		return
	}
	return
}

// Payment 提现打款处理
// 银行卡打款文档：https://www.joinpay.com/open-platform/pages/document.html?apiName=%E4%BB%A3%E4%BB%98%E4%BA%A7%E5%93%81&id=10
func (s *sAdminCash) Payment(ctx context.Context, in *adminin.CashPaymentInp) (err error) {

	// 权限验证
	if contexts.GetRoleId(ctx) != consts.RoleManage && !service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx)) {
		err = gerror.New("无操作权限")
		return
	}

	// 获取提现信息
	var models *entity.AdminCash
	if err = dao.AdminCash.Ctx(ctx).Where("id", in.Id).Scan(&models); err != nil {
		return
	}
	if models == nil {
		err = gerror.New("未找到提现信息")
		return
	}

	// 是否退回余额
	isRefund := true
	defer func() {
		// 不设置err，避免替换掉原因错误信息
		_ = dao.AdminCash.Ctx(ctx).Where("id", in.Id).Scan(&models)
		// 提现状态为错误，并且没有退款记录，则退回余额
		if models.Status == consts.CashStatusError && isRefund {
			if flag, err := dao.AdminCreditsLog.Ctx(ctx).
				Where(dao.AdminCreditsLog.Columns().CreditGroup, consts.CreditGroupBalanceRefund).
				Where(dao.AdminCreditsLog.Columns().MapId, models.Id).Exist(); err != nil {
				return
			} else if !flag {
				// 退回余额
				_, err = service.AdminCreditsLog().SaveBalance(ctx, &adminin.CreditsLogSaveBalanceInp{
					AppId:       contexts.GetModule(ctx),
					AddonsName:  contexts.GetAddonName(ctx),
					CreditGroup: consts.CreditGroupBalanceRefund,
					Num:         models.Money,
					MapId:       models.Id,
					StoreId:     models.StoreId,
					MemberId:    models.MemberId,
					Remark:      fmt.Sprintf("提现失败，余额退款"),
				})
			}
		}
	}()

	// 提现打款 或 通过审核
	if in.Status == consts.CashStatusPay {
		if models.WithdrawType == consts.WithdrawTypeWx { // 微信钱包，审核通过
			return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {

				// 获取用户信息
				mb, err := service.AdminMember().GetById(ctx, &adminin.GetByIdInp{
					models.MemberId,
				})
				if err != nil {
					return
				}

				notifyURL, err := service.Pay().GenNotifyURL(ctx, payin.PayCreateInp{
					PayType:    consts.PayTypeWxPay,       // 微信钱包
					OrderGroup: consts.OrderGroupTransfer, // 商家转账
				})
				if err != nil {
					return
				}

				config := payment.GetConfig()

				data := &entity.PayLog{
					MemberId:   models.MemberId,
					AppId:      "api",
					AddonsName: contexts.GetAddonName(ctx),
					//OrderSn:       in.OrderSn,
					//OrderMvSn:     in.OrderMvSn,
					OrderGroup: consts.OrderGroupTransfer, // 商家转账
					MchId:      config.WxPayMchId,
					Subject:    "用户提现",
					Openid:     mb.OpenId,
					//Detail:        in.Detail,
					OutTradeNo:    payment.GenOutTradeNo(),
					TransactionId: "",
					PayType:       consts.PayTypeWxPay,
					PayAmount:     models.LastMoney,
					PayStatus:     consts.PayStatusWait,
					TradeType:     consts.TradeTypeTransfer,
					IsRefund:      consts.RefundStatusNo,
					CreateIp:      location.GetClientIp(ghttp.RequestFromCtx(ctx)),
					NotifyUrl:     notifyURL,
					//ReturnUrl:     in.ReturnUrl,
					TraceIds:    gjson.New([]string{gctx.CtxId(ctx)}),
					Status:      consts.StatusEnabled,
					AdminCashId: models.Id,
				}

				res, err := payment.New(consts.PayTypeWxPay).Transfer(ctx, payin.CreateOrderInp{data})
				if err != nil {
					return
				}

				// 创建支付记录
				if _, err = dao.PayLog.Ctx(ctx).Data(data).OmitEmptyData().Insert(); err != nil {
					return
				}

				// 更新提现记录：审核通过
				_, err = dao.AdminCash.Ctx(ctx).Where(dao.AdminCash.Columns().Id, models.Id).Data(g.Map{
					dao.AdminCash.Columns().HandleAt:  gtime.Now(),
					dao.AdminCash.Columns().Status:    consts.CashStatusPay,
					dao.AdminCash.Columns().Msg:       in.Msg,
					dao.AdminCash.Columns().UpdatedBy: contexts.GetUserId(ctx),
					dao.AdminCash.Columns().Remark:    gjson.New(res).String(),
				}).Update()
				if err != nil {
					return gerror.New("更新提现记录失败")
				}
				// 不需要退回余额
				isRefund = false
				return
			})
		} else if models.WithdrawType == consts.WithdrawTypeBank { // 银行卡，提现打款
			if models.Status != consts.CashStatusWait {
				err = gerror.New("该提现已处理，不能再次操作！")
				return
			}

			// 支付配置
			payConfig := payment.GetConfig()

			// 获取账户余额
			accountBalanceResp, err := joinpay.AccountBalanceQuery(payConfig.AggJoinPayMerchantKey, joinpay.AccountBalanceReq{
				UserNo: payConfig.AggJoinPayUserNo,
			})
			if err != nil {
				return err
			}

			// 查询成功
			if accountBalanceResp.StatusCode == 2001 {
				// 账户余额不足
				if accountBalanceResp.Data.UseAbleSettAmount < models.LastMoney {
					return gerror.New(fmt.Sprintf("当前商户金额：%v,不足以支付，请到汇管家出金，或者到微信支付提现后再到汇管家出金", accountBalanceResp.Data.UseAbleSettAmount))
				}
			} else {
				// 获取账户余额失败，返回失败原因
				return gerror.New(accountBalanceResp.Message)
			}

			// 获取提现用户信息
			mb, err := service.AdminMember().GetById(ctx, &adminin.GetByIdInp{
				Id: models.MemberId,
			})
			if err != nil {
				return gerror.New("获取用户信息失败")
			}

			payDesc := "下楼休闲吧" + consts.GetRoleName(mb.RoleId)

			// 提现请求
			singlePayResp, err := joinpay.SinglePay(payConfig.AggJoinPayMerchantKey, joinpay.SinglePayReq{
				UserNo:                payConfig.AggJoinPayUserNo,
				ProductCode:           "BANK_PAY_DAILY_ORDER",
				RequestTime:           gtime.Now().String(),
				MerchantOrderNo:       models.MerchantOrderNo,
				ReceiverAccountNoEnc:  models.AccountNum,
				ReceiverNameEnc:       models.AccountName,
				ReceiverAccountType:   "201",
				ReceiverBankChannelNo: "",
				PaidAmount:            gconv.String(models.LastMoney),
				Currency:              "201",
				IsChecked:             "202",
				PaidDesc:              payDesc,
				PaidUse:               "201",
				CallbackUrl:           payConfig.AggJoinPayCallbackUrl,
			})
			if err != nil {
				return err
			}

			if singlePayResp != nil {
				// 提现请求成功
				if singlePayResp.StatusCode == 2001 {
					// 更新提现记录：打款中
					_, err = dao.AdminCash.Ctx(ctx).Where(dao.AdminCash.Columns().Id, models.Id).Data(g.Map{
						dao.AdminCash.Columns().HandleAt:  gtime.Now(),
						dao.AdminCash.Columns().Status:    consts.CashStatusPay,
						dao.AdminCash.Columns().Msg:       in.Msg,
						dao.AdminCash.Columns().UpdatedBy: contexts.GetUserId(ctx),
						dao.AdminCash.Columns().Remark:    singlePayResp,
					}).Update()
					if err != nil {
						return gerror.New("更新提现记录失败")
					}
					// 不需要退回余额
					isRefund = false
					// 等待回调响应
					time.Sleep(2 * time.Second)
					return nil
				} else {
					// 提现请求失败
					_, err = dao.AdminCash.Ctx(ctx).Where(dao.AdminCash.Columns().Id, models.Id).Data(g.Map{
						dao.AdminCash.Columns().HandleAt:  gtime.Now(),
						dao.AdminCash.Columns().Status:    consts.CashStatusError,
						dao.AdminCash.Columns().Msg:       singlePayResp.Data.ErrorDesc,
						dao.AdminCash.Columns().ErrMsg:    fmt.Sprint(singlePayResp.Data.ErrorCode, " - ", singlePayResp.Data.ErrorDesc),
						dao.AdminCash.Columns().UpdatedBy: contexts.GetUserId(ctx),
						dao.AdminCash.Columns().Remark:    singlePayResp,
					}).Update()
					if err != nil {
						return gerror.New("更新提现记录失败")
					}
					// 返回失败原因
					return gerror.New(singlePayResp.Data.ErrorDesc)
				}
			}
			return gerror.New("调用第三方支付失败")
		}

	} else if in.Status == consts.CashStatusError {

		res := &wechat.TransferBillsCancel{}

		if models.WithdrawType == consts.WithdrawTypeWx && models.Status == consts.CashStatusPay { // 微信提现并且审核已通过
			record, err2 := dao.PayLog.Ctx(ctx).Where(dao.PayLog.Columns().AdminCashId, models.Id).One()
			if err2 != nil || record.IsEmpty() {
				return gerror.New("获取提现支付记录失败")
			}

			res, err = payment.New(consts.PayTypeWxPay).TransferCancel(ctx, gconv.String(record.GMap().Get(dao.PayLog.Columns().OutTradeNo)))
			if err != nil {
				return
			}
		}

		data := g.Map{
			dao.AdminCash.Columns().HandleAt:  gtime.Now(),
			dao.AdminCash.Columns().Status:    consts.CashStatusError,
			dao.AdminCash.Columns().Msg:       in.Msg,
			dao.AdminCash.Columns().UpdatedBy: contexts.GetUserId(ctx),
		}

		if res.OutBillNo != "" {
			data[dao.AdminCash.Columns().Remark] = gjson.New(res).String()
		}

		// 拒绝打款，修改状态
		_, err = dao.AdminCash.Ctx(ctx).Where(dao.AdminCash.Columns().Id, models.Id).Data(data).Update()
		if err != nil {
			return gerror.New("更新提现记录失败")
		}
	} else if in.Status == 0 {
		_, err = dao.AdminCash.Ctx(ctx).Where(dao.AdminCash.Columns().Id, models.Id).Data(g.Map{
			dao.AdminCash.Columns().Msg:       in.Msg,
			dao.AdminCash.Columns().UpdatedBy: contexts.GetUserId(ctx),
		}).Update()
	}
	return
}

// 提现回调
func (s *sAdminCash) NotifyTransfer(ctx context.Context, in *payin.NotifyCallFuncInp) (err error) {

	// 获取提现信息
	var models *entity.AdminCash
	if err = dao.AdminCash.Ctx(ctx).WherePri(in.Pay.AdminCashId).Scan(&models); err != nil {
		return
	}
	if models == nil {
		err = gerror.New("未找到提现信息")
		return
	}

	data := g.Map{
		dao.AdminCash.Columns().HandleAt:  gtime.Now(),
		dao.AdminCash.Columns().UpdatedBy: contexts.GetUserId(ctx),
		dao.AdminCash.Columns().Remark:    in.Pay.Custom,
	}

	if in.Pay.PayStatus == consts.PayStatusOk {
		data[dao.AdminCash.Columns().Status] = consts.CashStatusSuccess
	} else if in.Pay.PayStatus == consts.PayStatusFail {
		data[dao.AdminCash.Columns().Status] = consts.CashStatusError
	}

	// 修改状态
	_, err = dao.AdminCash.Ctx(ctx).Where(dao.AdminCash.Columns().Id, models.Id).Data(data).Update()
	if err != nil {
		return gerror.New("更新提现记录失败")
	}

	return
}
